package org.test4j.integration.junit5;

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.extension.*;
import org.test4j.integration.ListenerFactory;
import org.test4j.mock.startup.JavaAgentHits;
import org.test4j.module.spring.SpringEnv;

import java.lang.reflect.Method;

public final class JUnit5Extension implements
    BeforeAllCallback, AfterAllCallback,
    BeforeEachCallback, AfterEachCallback,
    BeforeTestExecutionCallback, AfterTestExecutionCallback,
    TestInstancePostProcessor {
    static {
        JavaAgentHits.message();
    }

    @Override
    public void beforeAll(ExtensionContext context) {
        if (!isRegularTestClass(context)) {
            return;
        }

        Class testClass = context.getTestClass().orElse(null);
        if (SpringEnv.isSpringEnv(testClass)) {
            JUnit5SpringHelper.beforeAll(context);
        }
        ListenerFactory.beforeAll(testClass);
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        JUnit5SpringHelper.afterAll(context);
        if (isRegularTestClass(context)) {
            ListenerFactory.afterAll();
        }
    }

    @Override
    public void beforeEach(ExtensionContext context) throws Exception {
        Object target = context.getTestInstance().orElse(null);
        if (target != null) {
            ListenerFactory.beforeMethod(target);
        }
        JUnit5SpringHelper.beforeEach(context);
    }


    @Override
    public void afterEach(ExtensionContext context) throws Exception {
        ListenerFactory.afterMethod();
        JUnit5SpringHelper.afterEach(context);
    }

    @Override
    public void beforeTestExecution(ExtensionContext context) {
        Method testMethod = context.getTestMethod().orElse(null);
        Object testInstance = context.getTestInstance().orElse(null);
        if (testMethod != null && testInstance != null) {
            ListenerFactory.beforeExecute(testInstance, testMethod);
        }
    }

    @Override
    public void afterTestExecution(ExtensionContext context) {
        Method testMethod = context.getTestMethod().orElse(null);
        Object testInstance = context.getTestInstance().orElse(null);
        Throwable e = context.getExecutionException().orElse(null);
        ListenerFactory.afterExecute(testInstance, testMethod, e);
    }

    /**
     * Delegates to {@link org.springframework.test.context.TestContextManager#prepareTestInstance}.
     */
    @Override
    public void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception {
        SpringEnv.setSpringEnv(context.getRequiredTestClass());
        if (SpringEnv.isSpringEnv(context.getRequiredTestClass())) {
            SpringEnv.doSpringInitial(testInstance, context);
        }
    }

    private static boolean isRegularTestClass(ExtensionContext context) {
        Class testClass = context.getTestClass().orElse(null);
        return testClass != null && !testClass.isAnnotationPresent(Nested.class);
    }
}